'use strict';

const { Controller } = require('egg');

class TokenController extends Controller {
  requestAuthenticate(ctx) {
    ctx.set('WWW-Authenticate', 'Basic realm=\"CAS\"');
    ctx.throw(401, 'invalid_client');
  }

  getCsrf() {
    const { ctx } = this;
    const csrf = ctx.csrf;
    ctx.body = csrf;
    ctx.cookies.set('csrfToken', csrf, {
      sameSite: 'none',
      httpOnly: false,
      signed: false,
    });
  }

  async checkCode(ctx, client_id) {
    ctx.validate({
      code: 'string',
    });

    let { code, redirect_uri } = ctx.request.body;
    redirect_uri = redirect_uri || ctx.service.authorize.defaultRedirectUri(client_id);

    if (!await ctx.service.token.verifyCode(code, client_id, redirect_uri)) {
      ctx.throw(400, 'invalid_grant');
    }

    const token = await ctx.service.token.grant(code);
    if (token) {
      ctx.set('Cache-Control', 'no-store');
      ctx.set('Pragma', 'no-cache');
      ctx.body = token;
    } else {
      ctx.throw(500, 'ctx.model.Token.create() returns null');
    }
  }

  async checkRefreshToken(ctx, client_id) {
    ctx.validate({
      refresh_token: 'string',
    });

    const { refresh_token, scope } = ctx.request.body;

    const token = await ctx.service.token.getByRefreshToken(refresh_token);
    if (!token || !token.valid) {
      ctx.throw(400, 'invalid_grant');
    }

    const code = await ctx.service.token.getByCode(token.code);
    if (!code) {
      ctx.throw(500, 'Token found but code not found');
    }
    if (code.client_id !== client_id) {
      ctx.throw(400, 'invalid_grant');
    }

    const scopeJson = JSON.parse(code.scope);

    if (!ctx.service.token.verifyScope(scope, scopeJson)) {
      ctx.throw(400, 'invalid_scope');
    }

    const s = scope ? scope.split(' ') : scopeJson;

    const newToken = await ctx.service.token.refresh(token, s);

    if (newToken) {
      ctx.set('Cache-Control', 'no-store');
      ctx.set('Pragma', 'no-cache');
      ctx.body = newToken;
    } else {
      ctx.throw(500, 'ctx.model.Token.create() returns null');
    }
  }

  async token() {
    const { ctx } = this;
    let authorization = ctx.get('Authorization');
    if (authorization.indexOf('Basic ') !== 0) {
      this.requestAuthenticate(ctx);
      return;
    }

    authorization = authorization.slice(6);
    authorization = Buffer.from(authorization, 'base64').toString();
    authorization = authorization.split(':');
    const client_id = authorization[0];
    authorization.shift();
    const client_secret = authorization.join(':');
    if (!ctx.service.token.verifyClientSecret(client_id, client_secret)) {
      this.requestAuthenticate(ctx);
      return;
    }

    ctx.validate({
      grant_type: 'string',
    });

    try {
      ctx.validate({
        grant_type: [ 'authorization_code', 'refresh_token' ],
      });
    } catch (e) {
      ctx.throw(400, 'unsupported_grant_type');
    }

    const { grant_type } = ctx.request.body;

    switch (grant_type) {
      case 'authorization_code':
        await this.checkCode(ctx, client_id);
        break;
      case 'refresh_token':
        await this.checkRefreshToken(ctx, client_id);
        break;
      default:
        break;
    }
  }
}

module.exports = TokenController;
